﻿using System;
using BowlingKata;
using NUnit.Framework;

namespace BowlingGame_spec
{
    [TestFixture]
    public class Given_a_new_Game
    {
        [Test]
        public void TotalScore_is_zero()
        {
            Assert.AreEqual(0, new BowlingGame().TotalScore);
        }

        [Test]
        public void Representation()
        {
            Assert.AreEqual("-- -- -- -- -- -- -- -- -- --", new BowlingGame().ToString());
        }
    }

    [TestFixture]
    public class First_frame
    {
        [Test]
        public void Normal()
        {
            var game = new BowlingGame();
            game.Shot(6);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("6- -- -- -- -- -- -- -- -- --", game.ToString());

            game.Shot(2);

            Assert.AreEqual(8, game.TotalScore);
            Assert.AreEqual("62 -- -- -- -- -- -- -- -- --", game.ToString());
        }

        [Test]
        public void Zero()
        {
            var game = new BowlingGame();
            game.Shot(0);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("0- -- -- -- -- -- -- -- -- --", game.ToString());

            game.Shot(0);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 -- -- -- -- -- -- -- -- --", game.ToString());
        }

        [Test]
        public void Spare()
        {
            var game = new BowlingGame();
            game.Shot(3);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("3- -- -- -- -- -- -- -- -- --", game.ToString());

            game.Shot(7);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("3/ -- -- -- -- -- -- -- -- --", game.ToString());

            game.Shot(5);

            Assert.AreEqual(15, game.TotalScore);
            Assert.AreEqual("3/ 5- -- -- -- -- -- -- -- --", game.ToString());
        }

        [Test]
        public void Strike()
        {
            var game = new BowlingGame();
            game.Shot(10);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("X -- -- -- -- -- -- -- -- --", game.ToString());

            game.Shot(4);
            
            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("X 4- -- -- -- -- -- -- -- --", game.ToString());

            game.Shot(2);

            Assert.AreEqual(22, game.TotalScore);
            Assert.AreEqual("X 42 -- -- -- -- -- -- -- --", game.ToString());
        }

        [Test]
        [TestCase(11)]
        [TestCase(-1)]
        public void Invalid_first_shot(int pins)
        {
            var game = new BowlingGame();

            Assert.Throws<ArgumentOutOfRangeException>(() => game.Shot(pins));
        }

        [Test]
        [TestCase(0, 11)]
        [TestCase(1, 10)]
        [TestCase(5, 6)]
        [TestCase(5, -1)]
        public void Invalid_second_shot(int firstPins, int secondPins)
        {
            var game = new BowlingGame();
            game.Shot(firstPins);

            Assert.Throws<ArgumentOutOfRangeException>(() => game.Shot(secondPins));
        }
    }

    [TestFixture]
    public class Multiple_frames
    {
        [Test]
        public void Ordinary()
        {
            var game = new BowlingGame();
            game.Shot(0);
            game.Shot(1);
            game.Shot(5);
            game.Shot(4);

            Assert.AreEqual(10, game.TotalScore);
            Assert.AreEqual("01 54 -- -- -- -- -- -- -- --", game.ToString());
        }

        [Test]
        public void Double_Strike()
        {
            var game = new BowlingGame();
            game.Shot(10);
            game.Shot(10);
            game.Shot(4);
            game.Shot(1);

            Assert.AreEqual(44, game.TotalScore);
            Assert.AreEqual("X X 41 -- -- -- -- -- -- --", game.ToString());           
        }

        [Test]
        public void Triple_Strike()
        {
            var game = new BowlingGame();
            game.Shot(10);
            game.Shot(10);
            game.Shot(10);

            Assert.AreEqual(30, game.TotalScore);
            Assert.AreEqual("X X X -- -- -- -- -- -- --", game.ToString());

            game.Shot(3);
            game.Shot(1);

            Assert.AreEqual(71, game.TotalScore);
            Assert.AreEqual("X X X 31 -- -- -- -- -- --", game.ToString());
        }

        [Test]
        public void Spare_And_Strike()
        {
            var game = new BowlingGame();
            game.Shot(3);
            game.Shot(7);
            game.Shot(10);
            game.Shot(3);
            game.Shot(2);

            Assert.AreEqual(40, game.TotalScore);
            Assert.AreEqual("3/ X 32 -- -- -- -- -- -- --", game.ToString());
        }
    }

    [TestFixture]
    public class Last_frame
    {
        private BowlingGame game;

        [SetUp]
        public void Setup()
        {
            game = new BowlingGame();

            for (int i = 0; i < 18; ++i)
            {
                game.Shot(0);
            }
        }

        [Test]
        public void Normal()
        {
            game.Shot(2);
            game.Shot(7);

            Assert.AreEqual(9, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 27", game.ToString());

            Assert.Throws<BowlingGameException>(() => game.Shot(1));
        }

        [Test]
        public void Spare()
        {
            game.Shot(2);
            game.Shot(8);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 2/-", game.ToString());

            game.Shot(3);

            Assert.AreEqual(13, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 2/3", game.ToString());

            Assert.Throws<BowlingGameException>(() => game.Shot(1));
        }

        [Test]
        public void Spare_and_strike()
        {
            game.Shot(2);
            game.Shot(8);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 2/-", game.ToString());

            game.Shot(10);

            Assert.AreEqual(20, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 2/X", game.ToString());

            Assert.Throws<BowlingGameException>(() => game.Shot(1));
        }

        [Test]
        public void Strike_and_normal()
        {
            game.Shot(10);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 X--", game.ToString());

            game.Shot(3);
            game.Shot(2);

            Assert.AreEqual(15, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 X32", game.ToString());

            Assert.Throws<BowlingGameException>(() => game.Shot(1));
        }

        [Test]
        public void Strike_and_spare()
        {
            game.Shot(10);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 X--", game.ToString());

            game.Shot(3);
            game.Shot(7);

            Assert.AreEqual(20, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 X3/", game.ToString());

            Assert.Throws<BowlingGameException>(() => game.Shot(1));
        }

        [Test]
        public void Double_strike()
        {
            game.Shot(10);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 X--", game.ToString());

            game.Shot(10);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 XX-", game.ToString());

            game.Shot(3);
            
            Assert.AreEqual(23, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 XX3", game.ToString());

            Assert.Throws<BowlingGameException>(() => game.Shot(1));
        }

        [Test]
        public void Triple_Strike()
        {
            game.Shot(10);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 X--", game.ToString());

            game.Shot(10);

            Assert.AreEqual(0, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 XX-", game.ToString());

            game.Shot(10);

            Assert.AreEqual(30, game.TotalScore);
            Assert.AreEqual("00 00 00 00 00 00 00 00 00 XXX", game.ToString());

            Assert.Throws<BowlingGameException>(() => game.Shot(1));
        }
    }

    [TestFixture]
    public class Full_game
    {
        [Test]
        public void All_Strikes()
        {
            var game = new BowlingGame();

            for (int i = 0; i < 12; ++i )
            {
                game.Shot(10);                
            }

            Assert.AreEqual(300, game.TotalScore);
            Assert.AreEqual("X X X X X X X X X XXX", game.ToString());
        }

        [Test]
        public void Mixed()
        {
            var game = new BowlingGame();
            game.Shot(1);
            game.Shot(4);
            game.Shot(4);
            game.Shot(5);
            game.Shot(6);
            game.Shot(4);
            game.Shot(5);
            game.Shot(5);
            game.Shot(10);
            game.Shot(0);
            game.Shot(1);
            game.Shot(7);
            game.Shot(3);
            game.Shot(6);
            game.Shot(4);
            game.Shot(10);
            game.Shot(2);
            game.Shot(8);
            game.Shot(6);

            Assert.AreEqual(133, game.TotalScore);
            Assert.AreEqual("14 45 6/ 5/ X 01 7/ 6/ X 2/6", game.ToString());
        }
    }
}
